#include <iostream>
#include <string>
#include <sqlite3.h>
#include <stdlib.h>
#include <stdio.h>
#include <vector>
#include <fstream>

using namespace std;
////////////////////////////
// Attribute types
////////////////////////////
const int DOUBLE = 0;
const int STRING = 1;

////////////////////////////
// Forward Declarations 
////////////////////////////

/**
 * @brief Holds necessary information
 * about tables.
 */
struct TableDesc {
  string table_name_;
  int size_;
  int* attr_types_;
};

/**
 * Executes a given SQL command.
 * @param cmd A SQL command to be executed
 * @param db A pointer to the database to execute the command
 */
void executeSQLCommand(const string& cmd, sqlite3* db);

/**
 * Inserts into values into a given table
 * @param table_name Name of table
 * @param values The values representing a row to be
 * inserted into the table
 * @param db The database instance
 */
void insertInto(const string& table_name, const vector<string>& values, sqlite3* db);

/**
 * Get the string representation of a particular field
 * @param s A String to be converted to a field
 * @param type Type of the field
 */
string getStr(const string& s, const int& type);

/**
 * Create the tables (table names are hard coded)
 * @table_descs Descriptions of the tables to be created
 */
void createTables(vector<TableDesc>& table_descs);

/**
 * Fills an array with a fixed value
 */
void arrayFill(const int& size, const int& val, int arr[]);

/**
 * Splits a given string (representing a row) with repect to the table.
 * @param str A string that represents a row
 * @param table_desc Description of the table so we know how to split
 * @param del The delimeter to split the string ("^") in our case
 * @param values The return values
 */
void split(const string& str, const TableDesc& table_desc, const string& del, vector<string>& values);
/**
 * Prints a vector for debugging purpuses
 */
void print(vector<string>& vec);

/**
 * Removes the tildes (if exists) surrounding the string.
 */
void trimTildes(string& str);

/**
 * @brief Main appliation. 
 * Creates the tables and inserts the values inside the files
 * to the tables.
 * Create table statements are not hard coded instead it is
 * read from createtables.sql.
 * Doing the otherwise is easy because we have
 * executeSQLCommand function.
 */
int main() {
  
  system("sqlite3 nutrients.db < createtables.sql");
  // open a db
  sqlite3* db;
  sqlite3_open("nutrients.db", &db);
  executeSQLCommand("BEGIN TRANSACTION", db);
  ////////////////////////////

  vector<TableDesc> table_descs;
  createTables(table_descs);
  for (int i = 0; i < table_descs.size(); ++i) {
    TableDesc desc = table_descs[i];
    string table_file_name = "sr23/" + desc.table_name_ + ".txt";
    ifstream table_file(table_file_name.c_str());
    
    if (!table_file.is_open()) {
      cerr << "File " << table_file_name << " does not exist." << endl;
      exit(-1);
    }

    string line;
    while (getline(table_file,line)) {
	if (line != "") {
	  vector<string> values;
	  split(line.substr(0, line.length()-1), desc, "^", values);
	  insertInto(desc.table_name_, values, db);
	}
    }
    table_file.close();
  }
  
  for (int i = 0; i < table_descs.size(); ++i) {
    delete[] table_descs[i].attr_types_;
  }
  ////////////////////////////
  // close the db
  executeSQLCommand("END TRANSACTION", db);
  sqlite3_close(db);
  return 0;
}

////////////////////////////
// Helper Functions     
////////////////////////////

void print(const vector<string>& vec) {
  for(int i = 0; i < vec.size(); ++i)
    cout << vec[i] << "  ==  ";
  cout << endl;
}

void trimTildes(string& str) {
  int first = str.find_first_of("~");
  int last = str.find_last_of("~");
  if (first >= 0 && last >=0) {
    str = str.substr(first+1, last-first-1);
  }
}

void split(const string& str, const TableDesc& table_desc, const string& del, 
       vector<string>& values) {
  
  if (values.size() > 0) {
    cout << "problem" << endl;
    exit(1);
  }
  int n_dels = table_desc.size_; 
  string str_copy = str;
  int prev_index = -1;
  for (int i = 0; i < n_dels-1; ++i) {
    int cur_index = str_copy.find_first_of(del, prev_index+1);
    string value = str.substr(prev_index+1, cur_index - prev_index - 1);
    trimTildes(value);
    value = getStr(value, table_desc.attr_types_[i]);
    values.push_back(value);
    prev_index = cur_index;
  }
  
  string last_val = "";
  if (prev_index < str.length()-1) {
    last_val = str.substr(prev_index+1, str.length());
    trimTildes(last_val);
    last_val = getStr(last_val, table_desc.attr_types_[n_dels-1]);
  }
  values.push_back(last_val);
}

void createTables(vector<TableDesc>& table_descs) {
  TableDesc desc;
  
  //LANGDESC
  desc.table_name_ = "LANGDESC";
  desc.size_ = 2;
  desc.attr_types_ = new int[desc.size_];
  arrayFill(desc.size_, STRING, desc.attr_types_);
  table_descs.push_back(desc);
  
  //FD_GROUP
  desc.table_name_ = "FD_GROUP";
  desc.size_ = 2;
  desc.attr_types_ = new int[desc.size_];
  arrayFill(desc.size_, STRING, desc.attr_types_);
  table_descs.push_back(desc);

  //FOOD_DES
  desc.table_name_ = "FOOD_DES";
  desc.size_ = 14;
  desc.attr_types_ = new int[desc.size_];
  arrayFill(desc.size_, STRING, desc.attr_types_);
  desc.attr_types_[8] = desc.attr_types_[10] = desc.attr_types_[11] = desc.attr_types_[12] = desc.attr_types_[13] = DOUBLE;
  table_descs.push_back(desc);

  //FOOTNOTE
  desc.table_name_ = "FOOTNOTE";
  desc.size_ = 5;
  desc.attr_types_ = new int[desc.size_];
  arrayFill(desc.size_, STRING, desc.attr_types_);
  table_descs.push_back(desc);

  //LANGUAL
  desc.table_name_ = "LANGUAL";
  desc.size_ = 2;
  desc.attr_types_ = new int[desc.size_];
  arrayFill(desc.size_, STRING, desc.attr_types_);
  table_descs.push_back(desc);

  //NUTR_DEF
  desc.table_name_ = "NUTR_DEF";
  desc.size_ = 6;
  desc.attr_types_ = new int[desc.size_];
  arrayFill(desc.size_, STRING, desc.attr_types_);
  table_descs.push_back(desc);

  //SRC_CD 
  desc.table_name_ = "SRC_CD";
  desc.size_ = 2;
  desc.attr_types_ = new int[desc.size_];
  arrayFill(desc.size_, STRING, desc.attr_types_);
  table_descs.push_back(desc);
  
  //DERIV_CD
  desc.table_name_ = "DERIV_CD";
  desc.size_ = 2;
  desc.attr_types_ = new int[desc.size_];
  arrayFill(desc.size_, STRING, desc.attr_types_);
  table_descs.push_back(desc);
 
  //DATSRCLN
  desc.table_name_ = "DATSRCLN";
  desc.size_ = 3;
  desc.attr_types_ = new int[desc.size_];
  arrayFill(desc.size_, STRING, desc.attr_types_);
  table_descs.push_back(desc);
 
  //WEIGHT
  desc.table_name_ = "WEIGHT";
  desc.size_ = 7;
  desc.attr_types_ = new int[desc.size_]; 
  arrayFill(desc.size_, DOUBLE, desc.attr_types_);
  desc.attr_types_[0] = desc.attr_types_[1] = desc.attr_types_[3] = STRING;
  table_descs.push_back(desc);
 
  //DATA_SRC
  desc.table_name_ = "DATA_SRC";
  desc.size_ = 9;
  desc.attr_types_ = new int[desc.size_];
  arrayFill(desc.size_, STRING, desc.attr_types_);
  table_descs.push_back(desc);
  
  //NUT_DATA
  desc.table_name_ = "NUT_DATA";
  desc.size_ = 17;
  desc.attr_types_ = new int[desc.size_];
  arrayFill(desc.size_, STRING, desc.attr_types_);
  desc.attr_types_[2] = desc.attr_types_[3] = desc.attr_types_[4] = desc.attr_types_[9] = desc.attr_types_[10] = desc.attr_types_[11] = desc.attr_types_[12] = desc.attr_types_[13] = desc.attr_types_[14] = DOUBLE; 
  table_descs.push_back(desc);
}

void executeSQLCommand(const string& cmd, sqlite3* db) {
  char* err_msg;
  int rc = sqlite3_exec(db, cmd.c_str(), NULL, 0, &err_msg);
  if( rc != SQLITE_OK ){
    cerr << "SQL Error: " << err_msg << endl;
    sqlite3_free(err_msg);
    //exit(-1);
  }
}

void insertInto(const string& table_name, const vector<string>& values, sqlite3* db) {
  if (values.size() <= 0) {
    cerr << "Size of values must be larger than 0" << endl;
    exit(-1);
  }

  string query;

  query.append("INSERT INTO ");
  query.append(table_name);
  query.append(" VALUES (");
  
  for (int i = 0; i < values.size(); ++i) {
    if (values[i] == "\"\"" || values[i] == "" || values[i].length() == 0) {
      query.append("NULL");
    }
    else {
      query.append(values[i]);
    }

    if (i != values.size()-1) {
      query.append(",");
    }
  }

  query.append(");");
  //cout << "Inserted: " << query << endl;
  executeSQLCommand(query, db);
}

/*
void insertInto(const string& table_name, const vector<string>& values, sqlite3* db) {
  if (values.size() <= 0) {
    cerr << "Size of values must be larger than 0" << endl;
    exit(-1);
  }
 
  stringstream query;

  query << "INSERT INTO ";
  query << table_name;
  query << " VALUES (";
  
  for (int i = 0; i < values.size(); ++i) {
    if (values[i] == "\"\"" || values[i] == "") {
      query << "NULL";
    }
    else {
      query << values[i];
    }
    if (i != values.size()-1) {
      query << ",";
    }
  }

  query << ");";
  string q = query.str();
  cout << "Inserted: " << q << endl;
  //executeSQLCommand(q, db);
}
*/

string getStr(const string& s, const int& type) {
  if (type == STRING) {
    int indexDoubleQuote = s.find_first_of("\"");
    int indexSingleQuote = s.find_first_of("'");
    if (indexSingleQuote >= 0 && indexDoubleQuote >= 0) {
      string ret = "'" + s.substr(0,indexSingleQuote) + "''" + s.substr(indexSingleQuote+1, s.length()) + "'";
      return ret;
    }
    if (indexDoubleQuote >= 0) 
      return ("'" + s + "'");
    else
      return ("\"" + s + "\"");
  }
  
  return s;
}

void arrayFill(const int& size, const int& val, int arr[]) {
  for(int i = 0; i < size; ++i)
    arr[i] = val;
}
